import contextvars
import json
import string
import time
import uuid
from typing import Dict, Any, Optional, List, TypeVar

from fastapi import Request
from loguru import logger
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import Response

from app.core.config import settings

T = TypeVar("T", bound=Dict | List)


class RequestContext:
    def __init__(self) -> None:
        self.request_id: contextvars.ContextVar[Optional[str]] = contextvars.ContextVar(
            "request_id", default="-"
        )
        self.request_ip: contextvars.ContextVar[Optional[str]] = contextvars.ContextVar(
            "request_ip", default="-"
        )
        self.request_method: contextvars.ContextVar[Optional[str]] = (
            contextvars.ContextVar("request_method", default="-")
        )
        self.request_path: contextvars.ContextVar[Optional[str]] = (
            contextvars.ContextVar("request_path", default="-")
        )

    def get(self, key: str) -> Optional[str]:
        if isinstance(value := getattr(self, key), contextvars.ContextVar):
            return value.get()
        return None

    def set(self, key: str, value: Optional[str]):
        if isinstance(item := getattr(self, key), contextvars.ContextVar):
            item.set(value)


request_context: RequestContext = RequestContext()


class RequestLogMiddleware(BaseHTTPMiddleware):
    @staticmethod
    def get_short_id():
        buffer = string.ascii_letters + string.digits
        _id = uuid.uuid4().hex
        short_id = str()
        for i in range(0, 8):
            start = i * 4
            end = i * 4 + 4
            val = int(_id[start:end], 16)
            short_id += buffer[val % 62]
        return short_id

    def desensitization(self, data: T) -> T:
        if settings.debug:
            return data
        if isinstance(data, dict):
            for k, v in data.items():
                if k in settings.SENSITIVE_FIELD and isinstance(v, str):
                    data[k] = "******"
                elif isinstance(v, (dict, list)):
                    data[k] = self.desensitization(v)
        elif isinstance(data, list):
            for i, value in enumerate(data):
                data[i] = self.desensitization(value)
        return data

    def set_request_context(self, request: Request):
        request_id = request.headers.get("HTTP_X_REQUEST_ID", self.get_short_id())
        request_ip = self.get_request_ip(request)
        request_method = request.method
        request_path = str(request.url.path)
        request.scope.update(
            {
                "request_id": request_id,
                "request_ip": request_ip,
                "request_method": request_method,
                "request_path": request_path,
            }
        )
        request_context.set("request_id", request_id)
        request_context.set("request_ip", request_ip)
        request_context.set("request_method", request_method)
        request_context.set("request_path", request_path)

    @staticmethod
    def get_request_ip(request: Request) -> str:
        """
        获取客户端真实IP地址
        优先级顺序：
        1. X-Forwarded-For 头部（代理传递的真实客户端IP）
        2. X-Real-IP 头部
        3. CF-Connecting-IP 头部（Cloudflare）
        4. request.client.host（直接连接的客户端IP）
        """
        if forwarded_for := request.headers.get("x-forwarded-for"):
            if client_ip := forwarded_for.split(",")[0].strip():
                return client_ip

        if real_ip := request.headers.get("x-real-ip"):
            return real_ip

        if cf_ip := request.headers.get("cf-connecting-ip"):
            return cf_ip

        if x_original_forwarded_for := request.headers.get("x-original-forwarded-for"):
            return x_original_forwarded_for.split(",")[0].strip()

        if request.client:
            return request.client.host

        return "unknown"

    async def dispatch(self, request: Request, call_next) -> Response:
        self.set_request_context(request)
        headers = request.headers
        logger.debug(f"{self.__class__.__name__}.dispatch, {headers=}")
        start_time = time.perf_counter()

        # 打印请求数据
        request_data = await self._parse_request_data(request)
        logger.info(f"API Request Received, {request_data=}")

        # 打印响应数据
        response = await call_next(request)
        # 设置响应头
        request_id = request.scope["request_id"]
        cost = round((time.perf_counter() - start_time) * 1000, 2)
        response.headers["X-Response-Time"] = f"{cost}ms"
        response.headers["X-Request-ID"] = request_id
        response_data = await self._parse_response_data(response)
        logger.info(f"API Response Sent, {response_data=}, {cost=}ms")
        return response

    @staticmethod
    async def _parse_response_data(response: Response) -> Dict[str, Any]:
        response_data: Dict[str, Any] = {
            "status_code": response.status_code,
        }
        return response_data

    async def _parse_request_data(self, request: Request) -> Dict[str, Any]:
        try:
            if request_body := await request.body():
                logger.debug(f"获取json参数, {request_body=}")
                return self.desensitization(json.loads(request_body))
        except Exception as error:
            logger.warning(f"获取json参数失败, {error=}")
        data = {**request.path_params, **request.query_params}
        return self.desensitization(data)
